// ---------------------------
// projects/life/Life.h
// Copyright (C) 2007
// Glenn P. Downing
// ---------------------------

#ifndef Life_h
#define Life_h

#include <string>
#include <vector>    // vector
#include <cassert>   // assert
#include <algorithm> // swap

using namespace std;

//handle from class slides
template <typename T>
class Handle {
    friend bool operator == (const Handle& lhs, const Handle& rhs) {
        return (!lhs._p && !rhs._p) || (*lhs._p == *rhs._p);}

    friend bool operator != (const Handle& lhs, const Handle& rhs) {
        return !(lhs == rhs);}

    public:
        typedef T                 value_type;

        typedef value_type*       pointer;
        typedef const value_type* const_pointer;

        typedef value_type&       reference;
        typedef const value_type& const_reference;

    private:
        pointer _p;

    protected:
        pointer get () {
            return _p;}

        const_pointer get () const {
            return _p;}

    public:
        Handle (pointer p) {
            _p = p;}

        Handle (const Handle& that) {
            if (!that._p)
                _p = 0;
            else
                _p = that._p->clone();}

        ~Handle () {
            delete _p;}

        Handle& operator = (Handle that) {
            swap(that);
            return *this;}

        void swap (Handle& that) {
            std::swap(_p, that._p);}};

///Class Abstract_Cell
class Abstract_Cell {
	

  public:
	///pure virtual method getCharRepresentation, get a character representation of this Abstract_Cell
	///@return char representation of this Abstract_Cell
	virtual char getCharRepresentation() const = 0;

	///pure virtual method turn, kills cell, revives cell, cell stays dead, or cell stays alive
	///@param conwayNeighbors number of live neighbors for a Conway cell
	///@param fredkinNeighbors  number of live neighbors for a Fredkin cell
	///@return zero if cell is dead, one if cell is alive
	virtual int turn(int, int) = 0;

	///pure virtual method clone
	///@return a clone of this Abstract_Cell
	virtual Abstract_Cell* clone() = 0;

	///pure virtual method isAlive
	///@return int 0 if dead, nonzero if alive
	virtual int isAlive() const = 0;
};


///class ConwayCell inherits from Abstract_Cell
class ConwayCell : public Abstract_Cell {
    private:
	int state;//zero dead, nonzero alive
    public:
	///Default constructor, defaults to dead
	ConwayCell() : state(0){}
	///Constructor
	///@param c . for dead, anything else for alive.
	ConwayCell(char c) : state(c-46) {}//gives 0 for '.', nonzero otherwise
	///getCharRepresentation
	///@return the character representation of this ConwayCell
	char getCharRepresentation() const{
		if(state) return '*';
		return '.'; }
	///int turn, kills cell, revives cell, cell stays dead, or cell stays alive, based on number of live neighbors
	///@param conwayNeighbors number of live neighbors for a Conway cell
	///@param fredkinNeighbors number of live neighbors for a Fredkin cell
	///@return zero if cell is dead, one if cell is alive	
	int turn(int conwayNeighbors, int fredkinNeighbors){
		if(conwayNeighbors == 3) return state = 1;
		else if(conwayNeighbors == 2&&state) return 1;
		else return state = 0;}

	///clone method, return a close of this ConwayCell
	///@return a clone of this conway cell
	ConwayCell* clone() {
		return new ConwayCell(*this);}
	///method isAlive
	///@return nonzero if alive, zero if dead
	int isAlive() const{
		return state;}
};

///class FredkinCell child of Abstract_Cell
class FredkinCell : public Abstract_Cell {
    private:
        int age;
	int state;

    public:
        ///Default constructor, defaults to dead
	FredkinCell() : age(0), state(0){}
	///Constructor
	///@param c - for dead, or an int or + for alive.  The int is the age
	FredkinCell(char c){
		state = c != '-';
		age = 0;
		if(c=='+')
			age = 10;
		else if (c!='-') age = (c - '0');}

	///method getCharRepresentation
	///@return a char representation of this Fredkin Cell
	char getCharRepresentation() const{
		if(!state) return '-';
		if(age>9) return '+';
		return age + '0';}

	///int turn, kills cell, revives cell, cell stays dead, or cell stays alive, based on number of live neighbors
	///@param conwayNeighbors number of live neighbors for a Conway cell
	///@param fredkinNeighbors number of live neighbors for a Fredkin cell
	///@return zero if cell is dead, one if cell is alive	
	int turn(int conwayNeighbors, int fredkinNeighbors){
		if(state&&(fredkinNeighbors % 2)) age++;
		return state = fredkinNeighbors % 2;}

	///clone method, return a close of this FredkinCell
	///@return a clone of this conway cell
	FredkinCell* clone() {
		return new FredkinCell(*this);}

	///method isAlive
	///@return nonzero if alive, zero if dead
	int isAlive() const{
		return state;}
};

///Class Cell, handle for Abstract_Cell
class Cell : Handle<Abstract_Cell>{

	public:
	///Constructor
	///@param p an abstract cell pointer
	Cell (Abstract_Cell* p) : Handle<Abstract_Cell>(p){}

	///default constructor
	Cell (): Handle<Abstract_Cell>(0){}

	///method getCharRepresentation, get a character representation of this Cell
	///@return char representation of this Cell
	char getCharRepresentation() const{
		return get()->getCharRepresentation();}

	///method turn, kills cell, revives cell, cell stays dead, or cell stays alive
	///@param conwayNeighbors number of live neighbors for a Conway cell
	///@param fredkinNeighbors number of live neighbors for a Fredkin cell
	///@return zero if cell is dead, one if cell is alive
	int turn(int conwayNeighbors, int fredkinNeighbors){
		int result = get()->turn(conwayNeighbors, fredkinNeighbors);
		FredkinCell* fc;		
		//if a fredkin cell is turning two, convert it
		fc = dynamic_cast<FredkinCell*>(get());
		if(fc && result && fc->getCharRepresentation() =='2'){ 
			Cell tmp = ConwayCell('*').clone();
			swap(tmp);}
		return result;
	}	

	///method isAlive
	///@return int 0 if dead, nonzero if alive
	int isAlive() const{
		return get()->isAlive();}};


template <typename T> //T is either Fredkin Cell, Conway Cell, or Cell
class Life {

     private:
          const int rowStart;//start and end row index for use in loops, helps eliminate possible errors
          const int rowEnd;//start is inclusive, end is exclusive
	  const int colStart;//start and end column index for use in loops, helps eliminate possible errors
	  const int colEnd;//start is inclusive, end is exclusive
	  int population;//number of cells on grid
	  int generation;//number of generations elapsed
          vector<vector <T > > grid; // size is numRows+2, numCols+2, to allow for border
          vector<vector < int > > conwayIntGrid; //hold live neighbor count for conway cells
	  vector<vector < int > > fredkinIntGrid;//hold live neighbor count for fredkin cells
	
	void incrementNeighbors(int i, int j){
		conwayIntGrid[i-1][j-1]++;
		conwayIntGrid[i-1][j]++;
		conwayIntGrid[i-1][j+1]++;
		conwayIntGrid[i][j-1]++;
		conwayIntGrid[i][j+1]++;
		conwayIntGrid[i+1][j-1]++;
		conwayIntGrid[i+1][j]++;
		conwayIntGrid[i+1][j+1]++;
		fredkinIntGrid[i][j+1]++;
		fredkinIntGrid[i][j-1]++;
		fredkinIntGrid[i-1][j]++;
		fredkinIntGrid[i+1][j]++;}

     public:
	///overloaded operator <<, output a Life instance
	///@param lhs an ostream
	///@param rhs a Life instance
	///@return lhs an ostream
	friend std::ostream& operator << (std::ostream& lhs, const Life& rhs){
		lhs<<'\n'<<"Generation = "<<rhs.generation<<", Population = "<<rhs.population<<".\n";
		for(int i=rhs.rowStart; i<rhs.rowEnd; i++){
			for(int j=rhs.colStart; j<rhs.colEnd; j++)
				lhs<<rhs.grid[i][j].getCharRepresentation();
			lhs<<'\n';}
	      return lhs;}

	///readConwayorFredkin, reads in a grid of Conway or Fredkin cells of already specified size
	///@param in an istream
	void readConwayorFredkin(std::istream& in){
		for(int i=rowStart; i<rowEnd; i++)
			for(int j=colStart; j<colEnd; j++){
				char c;
				in>>c;
				if(c!='.'&&c!='-') population++;
				grid[i][j] = c;}}
	///readCellGrid read in a grid of Conway and Fredkin cells of already specified size
	///@param in an istream
	void readCellGrid(std::istream& in){
		for(int i=rowStart; i<rowEnd; i++)
			for(int j=colStart; j<colEnd; j++){
				char c;
				in>>c;
				if(c=='.'||c=='*')
					grid[i][j] = ConwayCell(c).clone();
				else
					grid[i][j] = FredkinCell(c).clone();
				if(c!='.' && c!='-')
					population++;}

	}
        
	///Life constructor
	///@param  x number of rows
	///@param  y number of columns
	Life( int x, int y): rowStart(1), rowEnd(x+1), colStart(1), colEnd(y+1),population(0), generation(0),
             grid(x+2, vector<T>(y+2)),
             conwayIntGrid(x+2, vector<int>(y+2, 0)),
	     fredkinIntGrid(x+2, vector<int>(y+2, 0)){}

	///method turn
	///goes through grid and simulates one generation of Life
         void turn() {
		population = 0;
		generation++;
		for(int i=rowStart; i<rowEnd; i++)
			for(int j=colStart; j<colEnd; j++)
				if(grid[i][j].isAlive()) incrementNeighbors(i, j);
		for(int i=rowStart; i<rowEnd; i++)
			for(int j=colStart; j<colEnd; j++){
				population+=grid[i][j].turn(conwayIntGrid[i][j], fredkinIntGrid[i][j]);
				conwayIntGrid[i][j] = 0;
				fredkinIntGrid[i][j] = 0;}
	}


};


#endif // Life_h
